package com.leyou.search.service.impl;

import com.leyou.common.constants.ESIndexConstants;
import com.leyou.common.dto.PageDTO;
import com.leyou.common.exception.LyException;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SkuDTO;
import com.leyou.item.dto.SpecParamDTO;
import com.leyou.item.dto.SpuDTO;
import com.leyou.search.dto.SearchParamDTO;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.SearchService;
import com.leyou.starter.elastic.entity.PageInfo;
import com.netflix.discovery.converters.Auto;

import static com.leyou.search.constants.SearchConstants.*;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.stream.Collectors;

@SuppressWarnings("ALL")
/**
 * @Description :
 * @author     :LD
 */
@Slf4j
@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    private ItemClient itemClient;

    @Override
    public void createIndexAndMapping() {
        //删除已存在的索引库
        try {
            goodsRepository.deleteIndex();
        } catch (Exception e) {
            log.error("删除失败，可能索引库不存在！", e);
        }
        //创建一个新的索引库
        goodsRepository.createIndex(ESIndexConstants.CREATE_GOODS_INDEX);
    }

    @Override
    public void loadDate() {
        int page = 1, rows = 100;
        while (true) {
            log.info("开始导入第{}页数据", page);
            //分页查询上架 的spu
            PageDTO<SpuDTO> result = itemClient.querySpuByPage(page, rows, true, null, null, null);
            List<SpuDTO> list = result.getItems();
            // 遍历Spu集合，把SpuDTO通过buildGoods方法转为Goods
            List<Goods> goodsList = list.stream()
                    .map(this::buildGoods).collect(Collectors.toList());

            // 批量写入Elasticsearch
            goodsRepository.saveAll(goodsList);
            log.info("导入第{}页数据结束", page);
            page++;

            //数据插入完成跳出循环
            Long totalPage = result.getTotalPage();
            if (page > totalPage) {
                break;
            }
        }
    }

    @Override
    public Mono<List<String>> getSuggestion(String key) {
        if (StringUtils.isBlank(key)) {
            throw new LyException(400, "请求参数不能为空");
        }
        return goodsRepository.suggestBySingleField(SUGGESTION_FIELD, key);
    }

    @Override
    public Mono<PageInfo<Goods>> searchGoods(SearchParamDTO request) {
        //1 构建查询对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1.1 source过滤
        sourceBuilder.fetchSource(DEFAULT_SOURCE_FIELD, new String[0]);
        //1.2 查询条件
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(400, "查询条件不能为空");
        }
        sourceBuilder.query(boolQuery.must(QueryBuilders.termQuery(DEFAULT_SEARCH_FIELD,key)));

        //过滤条件
        Map<String, String> filters = request.getFilters();
        if (filters!=null&&filters.size()!=0){
            for (Map.Entry<String, String> entry : filters.entrySet()) {
                String k = entry.getKey();
                String v = entry.getValue();
                if ("分类".equals(k)){
                    sourceBuilder.query(boolQuery.filter(QueryBuilders.termQuery("categoryId",Integer.parseInt(v))));
                }else if ("品牌".equals(k)){
                    sourceBuilder.query(boolQuery.filter(QueryBuilders.termQuery("brandId", Integer.parseInt(v))));
                }else {

                    sourceBuilder.query(boolQuery.filter(QueryBuilders.nestedQuery("specs",
                            QueryBuilders.boolQuery().must(QueryBuilders.termQuery("specs.name",k)).must(QueryBuilders.termQuery("specs.value",v))
                            ,ScoreMode.None)));
                }
            }
        }
        //1.3 分页条件
        sourceBuilder.from(request.getFrom());
        sourceBuilder.size(request.getSize());
        //1.4 排序条件
        if (StringUtils.isNotBlank(request.getSortBy())) {
            //如果有排序条件，默认降序
            sourceBuilder.sort(request.getSortBy(), request.getDesc() ? SortOrder.DESC : SortOrder.ASC);
        }
        //1.5 高亮条件
        sourceBuilder.highlighter(new HighlightBuilder().field(DEFAULT_SEARCH_FIELD).preTags(DEFAULT_PRE_TAG).postTags(DEFAULT_POST_TAG));
        return goodsRepository.queryBySourceBuilderForPageHighlight(sourceBuilder);
    }

    @Override
    public void saveGoodsById(Long id) {
        //根据id查询spu
        SpuDTO spu = itemClient.queryGoodsById(id);
        //构建goods
        Goods goods = buildGoods(spu);
        //写入索引库

        goodsRepository.save(goods);
    }

    @Override
    public void deleteGoodsById(Long id) {
        goodsRepository.deleteById(id);
    }

    @Override
    public Mono<Map<String, List<?>>> filterGood(SearchParamDTO request) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //1 根据前台传递的key来匹配
        sourceBuilder.query(QueryBuilders.matchQuery("title", request.getKey()).operator(Operator.AND)).size(0);
        //2 根据分类分桶
        sourceBuilder.aggregation(AggregationBuilders.terms("aggCategory").field("categoryId").size(50));
        //3 根据品牌分桶
        sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg").field("brandId").size(50));
        //4 根据规格参数分桶
        sourceBuilder.aggregation(AggregationBuilders.nested("specsAgg", "specs")
                .subAggregation(AggregationBuilders.terms("nameAgg").field("specs.name").size(100)
                        .subAggregation(AggregationBuilders.terms("valueAgg").field("specs.value").size(100))));
        return goodsRepository.aggregationBySourceBuilder(sourceBuilder).map(aggregations -> {
            Map<String, List<?>> map = new LinkedHashMap<>();
            //解析分类桶
            Terms categoryAgg = aggregations.get("aggCategory");
            List<Long> categoryIds = categoryAgg.getBuckets().stream()
                    .map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber().longValue())
                    .collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(categoryIds)){
                map.put("分类",itemClient.queryCategoryByIds(categoryIds));
            }
            //解析品牌桶
            Terms brandAgg = aggregations.get("brandAgg");
            List<Long> brandIds = brandAgg.getBuckets().stream()
                    .map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber().longValue())
                    .collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(brandIds)){
                map.put("品牌",itemClient.queryBrandByIds(brandIds));
            }
            //解析规格参数
            Nested specsAgg = aggregations.get("specsAgg");
            Terms nameAgg = specsAgg.getAggregations().get("nameAgg");
            nameAgg.getBuckets().forEach(bucket -> {
                String name = bucket.getKeyAsString();
                Terms valueAgg = bucket.getAggregations().get("valueAgg");
                List<String> values = valueAgg.getBuckets().stream().map(bucket1 -> bucket1.getKeyAsString()).collect(Collectors.toList());
                map.put(name,values);
            });

            return map;
        });
    }

    /**
     * 构建Goods
     *
     * @param spu
     * @return
     */
    private Goods buildGoods(SpuDTO spu) {
        // 1.自动补全的提示字段
        List<String> suggestion;
        if (StringUtils.isNotEmpty(spu.getCategoryName())) {
            suggestion = new ArrayList<>(
                    Arrays.asList(StringUtils.split(spu.getCategoryName(), "/")));
        } else {
            suggestion = new ArrayList<>();
        }
        suggestion.add(spu.getName());
        if (StringUtils.isNotEmpty(spu.getBrandName())) {
            suggestion.add(spu.getBrandName());
        }

        // 2.sku的价格集合
        // 2.1.查询sku集合
        List<SkuDTO> skuList = spu.getSkus();
        if (CollectionUtils.isEmpty(skuList)) {
            // 没有sku，我们去查询
            skuList = itemClient.querySkuBySpuId(spu.getId());
        }
        // 2.2.获取价格集合
        Set<Long> prices = skuList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        // 3.商品销量
        long sold = skuList.stream().mapToLong(SkuDTO::getSold).sum();

        // 4.sku的某个图片
        String image = StringUtils.substringBefore(skuList.get(0).getImages(), ",");

        // 5.规格参数
        List<Map<String, Object>> specs = new ArrayList<>();
        // 5.1.查询规格参数name和value键值对，只查询参与搜索的
        List<SpecParamDTO> params = itemClient.querySpecsValues(spu.getId(), true);
        // 5.2.封装
        for (SpecParamDTO param : params) {
            Map<String, Object> map = new HashMap<>(2);
            map.put("name", param.getName());
            map.put("value", chooseSegment(param));
            specs.add(map);
        }

        // 创建Goods对象，并封装数据
        Goods goods = new Goods();
        goods.setUpdateTime(new Date());
        // 自动补全的提示字段
        goods.setSuggestion(suggestion);
        // 规格参数
        goods.setSpecs(specs);
        // 商品销量
        goods.setSold(sold);
        // 商品标题
        goods.setTitle(spu.getTitle() + StringUtils.join(suggestion, " "));
        // sku的价格集合
        goods.setPrices(prices);
        // sku的某个图片
        goods.setImage(image);
        goods.setCategoryId(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setId(spu.getId());
        return goods;
    }

    /**
     * 处理数字value
     *
     * @param p
     * @return
     */
    private Object chooseSegment(SpecParamDTO p) {
        Object value = p.getValue();
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        if (!p.getNumeric() || StringUtils.isBlank(p.getSegments()) || value instanceof Collection) {
            return value;
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }
}
